package com.gton.person.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gton.person.entity.Role;
import com.gton.person.entity.Student;
import com.gton.person.entity.StudentUserInfo;
import com.gton.person.mapper.StudentMapper;
import com.gton.person.service.RoleService;
import com.gton.person.service.StudentService;
import com.gton.person.service.StudentUserInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 服务实现类
 * 在方法/实现类/接口上添加@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED)
 * 默认只回滚RuntimeException，检查异常则不回滚，需要@Transactional(rollbackFor=Exception.class）表示任何异常都回滚
 * propagation：事物传播行为配置，可参考org.springframework.transaction.annotation.Propagation,默认REQUIRED
 * <p>
 * REQUIRED：支持当前事务，如果当前没有事务，就新建一个事务。这是最常见的选择。
 * <p>
 * SUPPORTS：支持当前事务，如果当前没有事务，就以非事务方式执行。
 * <p>
 * MANDATORY：支持当前事务，如果当前没有事务，就抛出异常。
 * <p>
 * REQUIRES_NEW：新建事务，如果当前存在事务，把当前事务挂起。
 * <p>
 * NOT_SUPPORTED：以非事务方式执行操作，如果当前存在事务，就把当前事务挂起。
 * <p>
 * NEVER：以非事务方式执行，如果当前存在事务，则抛出异常。
 * <p>
 * NESTED：支持当前事务，如果当前事务存在，则执行一个嵌套事务，如果当前没有事务，就新建一个事务。
 * <p>
 * isolation：隔离级别，默认DEFAULT，选项有（DEFAULT，READ_UNCOMMITTED，READ_COMMITTED，REPEATABLE_READ，SERIALIZABLE）
 * <p>
 * DEFAULT 使用后端数据库默认的隔离级别(Mysql默认为REPEATABLE_READ )。
 * <p>
 * READ_UNCOMMITTED 允许读取尚未提交的更改。可能导致脏读、幻读或不可重复读。
 * <p>
 * COMMITTED 允许从已经提交的并发事务读取。可防止脏读，但幻读和不可重复读仍可能会发生
 * <p>
 * REPEATABLE_READ 对相同字段的多次读取的结果是一致的，除非数据被当前事务本身改变。可防止脏读和不可重复读，但幻读仍可能发生。
 * *
 * ISOLATION_SERIALIZABLE
 * </p>
 *
 * @author gton
 * @since 2020-11-25
 */
@Service
@Slf4j
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService {

    /**
     * 依赖注入：student
     */
    @Resource
    private StudentMapper studentMapper;


    /**
     * 依赖注入用户信息
     */
    @Autowired
    private StudentUserInfoService studentUserInfoService;

    /**
     * 依赖注入权限
     */

    @Resource
    private RoleService roleService;


    /**
     * jackson的JSON转换器
     */
    private final ObjectMapper objectMapper = new ObjectMapper();


    /**
     * 注入依赖：这里的这个是redis的模板依赖，方便我们java对于redis操作
     */
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * readOnly:指定事务是否为只读事务，默认值为 false；为了忽略那些不需要事务的方法，比如读取数据，可以设置 read-only 为 true。
     * propagation：事物传播行为配置 默认-：REQUIRED：支持当前事务，如果当前没有事务，就新建一个事务。这是最常见的选择。
     * rollbackFor :出现什么异常时回滚
     *
     * @param student
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED, readOnly = false)
    public boolean registerUser(Student student) {
        /*阿里推荐的方式属于自动提交/手动回滚阿里piapia规范推荐：事务场景中，抛出异常被catch后，如果需要回滚，一定要手动回滚事务。*/
        //直接保存用户信息
        boolean result = false;

        try {
            int sutdneInsertRols = studentMapper.insert(student);
            //构建权限表信息
            Role role = new Role();
            role.setSid(student.getId());
            role.setName("ROLE_vip1");
            //保存权限 表
            boolean roleTableInsertIsSuccess = roleService.insertRoleByRegister(role);
            //返回成功还是失败
            result = sutdneInsertRols > 0 && roleTableInsertIsSuccess ? true : false;
            if (result) {
                //如果添加成功，刷新缓存
                roleService.updateRedisForDML();
            }
            log.info("用户注册和权限初始化成功");
        } catch (Exception ex) {
            //手动抛异常：手动回滚
            throw ex;

        }
        return result;

    }

    @Override
    public String getResultAdvice(Student student) {
        System.out.println("校验用户数据中。。。。。");
        if (StringUtils.isEmpty(student.getUsername())) {
            return "您现在的操作-非法操作：必须携带用户名";
        }
        if (StringUtils.isEmpty(student.getPassword())) {
            return "您现在的操作-非法操作：必须携带密码";
        }
        //在redis中获取用户名：比对该注册的用户是否已经存在
        System.out.println("当前用户预想设置的姓名:" + student.getUsername());
        List<String> byRedisName = roleService.getByRedisName("userNameList");
        long count = Optional.ofNullable(byRedisName).orElseGet(() -> {
            System.out.println("redis中获取用户名 是空的,联系工作人员");
            return new ArrayList<>();
        }).stream().filter(Objects::nonNull).filter(name -> name.equalsIgnoreCase(student.getUsername())).count();
        System.out.println(":->数据库存在个数" + count);
        if (count > 0) {
            return "您输入的用户名已经被注册，请重新取一个";
        } else {
            System.out.println("很好。。。名字可以注册");
            return null;
        }

    }


    /**
     * 把用户ID和用户名，活用登录ID存入Redis，常用的不要频繁的访问数据MYSQL数据库
     *
     * @return Redis
     */
    @Override
    public boolean setUserIdAndUserNameAndLoginIdInRedis() {

        //第一步：判断redis是否已经存在
        //取出redis中的用户列表
        Object getredis = redisTemplate.opsForValue().get("userIdAndName");
        // 如果不存在
        if (StringUtils.isEmpty(getredis)) {
            //获取全部的用户
            List<StudentUserInfo> userInfos = studentUserInfoService.list();

            //封装容器初始化
            List<Map<String, Object>> userIdAndName = new ArrayList<>();
            //获取用户的ID和名字||登录时的ID
            for (StudentUserInfo userInfo : userInfos) {
                String id = userInfo.getId();
                String useName = userInfo.getUseName();
                Integer sid = userInfo.getSid();
                //数据装载入Map
                Map<String, Object> tempMap = new HashMap<>();
                tempMap.put("id", id);
                tempMap.put("useName", useName);
                tempMap.put("sid", sid);
                //Map装载数据继续装载入List
                userIdAndName.add(tempMap);
            }
            //2.将数据存入redis
            log.info("加载userIdAndName缓存redis->:" + userIdAndName);
            try {
                //先把   List<Map<String, Object>> JSON转化
                String asString = objectMapper.writeValueAsString(userIdAndName);
                redisTemplate.opsForValue().set("userIdAndName", asString);
                //设置过期时间； TimeUnit.MILLISECONDS  毫秒:设置默认时间是SECONDS:秒
                redisTemplate.expire("userIdAndName", 12, TimeUnit.HOURS);
                return true;
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }

        }
        log.info("加载userIdAndName缓存redis->:已完成！！");
        return true;
    }


    //获取全部的  获取用户的ID和名字||登录时的ID   redis
    @Override
    public List<Map<String, Object>> getRedisListMap(String RedisKeyName) {
        //Object getRedis = redisTemplate.opsForValue().get("userIdAndName");
        Object getRedis = redisTemplate.opsForValue().get(RedisKeyName);
        //json转为list
        List<Map<String, Object>> list = null;
        try {
            //先把redis中取出的数据转为字符串，然后转为list
            //使用object.toString()方法把Object转为字符串的话要注意Object不能为null否则会报NullPointException，一般别用这种方法。
            list = objectMapper.readValue(String.valueOf(getRedis), new TypeReference<List<Map<String, Object>>>() {
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

    //根据UserID --->   Redi名字和ID信息
    @Override
    public Map<String, Object> getRedisUserMapById(String UserId) {
        List<Map<String, Object>> list = getRedisListMap("userIdAndName");
        //返回的结果集
        Map<String, Object> resultMap = null;
        try {
            //如果list取出来没问题
            for (Map<String, Object> map : list) {
                Object id = map.get("id");
                if (!StringUtils.isEmpty(id)) {
                    //找出当前查询map信息
                    if (id.toString().equals(UserId)) {
                        resultMap = map;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return resultMap;
    }

    //根据loginID--->   Redi名字和ID信息
    @Override
    public Map<String, Object> getRedisUserMapByLoginId(String loginId) {
        List<Map<String, Object>> list = getRedisListMap("userIdAndName");
        //返回的结果集
        Map<String, Object> resultMap = null;
        try {
            //如果list取出来没问题
            for (Map<String, Object> map : list) {
                Object sid = map.get("sid");
                if (!StringUtils.isEmpty(sid)) {
                    //找出当前查询map信息
                    if (sid.toString().equals(loginId)) {
                        resultMap = map;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return resultMap;
    }

    //根据名字 --->Redi名字和ID信息
    @Override
    public Map<String, Object> getRedisUserMapByName(String userName) {
        List<Map<String, Object>> list = getRedisListMap("userIdAndName");
        //返回的结果集
        Map<String, Object> resultMap = null;
        try {
            //如果list取出来没问题
            for (Map<String, Object> map : list) {
                Object Name = map.get("useName");
                if (!StringUtils.isEmpty(Name)) {
                    //找出当前查询map信息
                    if (Name.toString().equals(userName)) {
                        resultMap = map;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return resultMap;
    }

}
